# cython: c_string_type=unicode, c_string_encoding=utf8, language_level=3

# pylinphone.pyx
# Copyright (c) 2010-2024 Belledonne Communications SARL.
#
# This file is part of Liblinphone 
# (see https://gitlab.linphone.org/BC/public/liblinphone).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import cython
import sys
import weakref

from libc.time cimport time, time_t

cdef extern from "bctoolbox/list.h":
    ctypedef struct bctbx_list_t:
        pass

    bctbx_list_t * bctbx_list_next(const bctbx_list_t * elem)
    void * bctbx_list_get_data(const bctbx_list_t * elem)
    bctbx_list_t * bctbx_list_append(bctbx_list_t * elem, void * data)
    bctbx_list_t * bctbx_list_free(bctbx_list_t * elem)
    size_t bctbx_list_size(const bctbx_list_t * first)
    void * bctbx_list_nth_data(const bctbx_list_t * _list, int index)
    LinphoneFactory * linphone_factory_get()

cdef class BctbxList:
    cdef bctbx_list_t * native_ptr
    cdef ownPtr

    def __init__(self):
        self.native_ptr = NULL
        self.ownPtr = False

    def __dealloc__(self):
        if self.native_ptr is not NULL and self.ownPtr:
            bctbx_list_free(self.native_ptr)
        self.native_ptr = NULL

    @staticmethod
    cdef BctbxList from_ptr(const bctbx_list_t * _ptr, _own=False):
        cdef BctbxList this = BctbxList.__new__(BctbxList)
        this.native_ptr = _ptr
        this.ownPtr = _own
        return this

    @staticmethod
    cdef BctbxList from_string_array(list _array):
        cdef bctbx_list_t * bctbx_list = NULL
        for elem in _array or []:
            elem_ptr = <char *>elem
            bctbx_list = bctbx_list_append(bctbx_list, elem_ptr)

        cdef BctbxList this
        this = BctbxList.__new__(BctbxList)
        this.native_ptr = bctbx_list
        this.ownPtr = True
        return this

    @property
    def size(self):
        return bctbx_list_size(self.native_ptr)

    cdef void * elem_at(self, i):
        cdef void * elem = bctbx_list_nth_data(self.native_ptr, i)
        return elem

    cdef append(self, void * data):
        self.native_ptr = bctbx_list_append(self.native_ptr, data)

    def to_string_array(self):
        plist = []
        it = self.native_ptr
        while it is not NULL:
            elem = <char *>bctbx_list_get_data(it)
            if elem is not None:
                plist.append(elem)
            it = bctbx_list_next(it)
        return plist

cdef extern from "belle-sip/object.h":
    ctypedef struct belle_sip_object_t:
        pass

    ctypedef void(*belle_sip_data_destroy)(void * data)

    void belle_sip_object_unref(belle_sip_object_t * obj)
    belle_sip_object_t * belle_sip_object_ref(belle_sip_object_t * obj)
    void * belle_sip_object_data_get(belle_sip_object_t * obj, const char * name)
    int belle_sip_object_data_set(belle_sip_object_t * obj, const char * name, void * data, belle_sip_data_destroy destroy_func)
    void belle_sip_object_data_remove(belle_sip_object_t * obj, const char * name)

# Force this header file to be included in generated C file
cdef extern from "linphone/tunnel.h":
    pass

# Force this header file to be included in generated C file
cdef extern from "linphone/core_utils.h":
    pass

# Force this header file to be included in generated C file
cdef extern from "linphone/api/c-api.h":
    pass

cdef extern from "linphone/chat.h":
    pass

cdef extern from "linphone/core.h":
    {{#c_classes}}
    ctypedef struct {{c_class_name}}:
        pass

    {{^is_factory}}
    void {{unref}}({{c_class_name}}* ptr)
    {{c_class_name}}* {{ref}}({{c_class_name}}* ptr)
    {{/is_factory}}

    {{/c_classes}}
    {{#c_enums}}
    ctypedef enum {{c_enum_name}}: {{c_enum_values}}
    {{/c_enums}}

    {{#c_callbacks}}
    ctypedef {{c_return_type}}(*{{c_name}})({{c_params}})

    {{/c_callbacks}}
    {{#c_methods}}
    {{c_prototype}}
    {{/c_methods}}

{{#enums}}
cpdef enum {{name}}:
    {{#values}}
    {{name}}{{value_name}} = {{value}}
    {{/values}}

{{/enums}}

{{#objects}}
cdef class {{python_name}}:
    cdef object __weakref__
    cdef {{c_name}}* native_ptr
    cdef wref
    {{#callbacks}}
    cdef _{{callback_var_name}}
    {{/callbacks}}

    {{#is_factory}}
    @staticmethod
    def get():
        return Factory.from_ptr(linphone_factory_get(), False)
    {{/is_factory}}

    def __init__(self):
        self.native_ptr = NULL

    def __dealloc__(self):
        if self.native_ptr is not NULL:
            belle_sip_object_data_remove(<belle_sip_object_t *>self.native_ptr, 'python_user_data')
            {{^is_factory}}
            {{unref}}(self.native_ptr)
            {{/is_factory}}
        self.native_ptr = NULL

    @staticmethod
    cdef {{python_name}} from_ptr({{c_name}} * _ptr, take_ref=True):
        cdef {{python_name}} this
        if _ptr is NULL:
            return None
        user_data = belle_sip_object_data_get(<belle_sip_object_t *>_ptr, 'python_user_data')

        if user_data is not NULL:
            tmp_wref = <object>user_data
            this = tmp_wref()
            if this is None:
                this = {{python_name}}.__new__({{python_name}})
                {{#is_factory}}
                this.native_ptr = _ptr
                {{/is_factory}}
                {{^is_factory}}
                this.native_ptr = {{ref}}(<{{c_name}} *> _ptr) if take_ref else _ptr
                {{/is_factory}}
            
                this.wref = weakref.ref(this)
                belle_sip_object_data_set(<belle_sip_object_t *>_ptr, 'python_user_data', <void *>this.wref, NULL)
        else:
            this = {{python_name}}.__new__({{python_name}})
            {{#is_factory}}
            this.native_ptr = _ptr
            {{/is_factory}}
            {{^is_factory}}
            this.native_ptr = {{ref}}(<{{c_name}} *> _ptr) if take_ref else _ptr
            {{/is_factory}}

            this.wref = weakref.ref(this)
            belle_sip_object_data_set(<belle_sip_object_t *>_ptr, 'python_user_data', <void *>this.wref, NULL)

        {{#callbacks}}
        {{callback_setter}}(this.native_ptr, &{{python_name}}.{{callback_internal_name}})
        {{/callbacks}}

        return this

    {{#callbacks}}
    @staticmethod
    cdef {{callback_return_type}} {{callback_internal_name}}({{c_params}}) noexcept:
        {{#params}}
        {{#is_obj_list}}
        bctbx_list = BctbxList.from_ptr({{python_param_name}})
        {{python_param_name}}_list = []
        for i in range(0, bctbx_list.size):
            pt = {{python_param_type}}.from_ptr(<{{param_c_type}}>bctbx_list.elem_at(i))
            {{python_param_name}}_list.append(pt)
        {{/is_obj_list}}
        {{#is_string_list}}
        bctbx_list = BctbxList.from_ptr({{python_param_name}})
        {{python_param_name}}_list = bctbx_list.to_string_array()
        {{/is_string_list}}
        {{#is_obj}}
        {{python_param_name}}_obj = {{python_param_type}}.from_ptr({{python_param_name}})
        {{/is_obj}}
        {{#is_bool}}
        {{python_param_name}}_b = {{python_param_name}} == 1
        {{/is_bool}}
        {{/params}}

        {{#is_multi_listener}}
        callbacks = {{first_python_param_name}}_obj.current_callbacks
        {{/is_multi_listener}}

        if callbacks.{{callback_var_name}} is not None:
            callbacks.{{callback_var_name}}({{computed_params}})
        {{#callback_has_return}}return NULL{{/callback_has_return}} # This is a workaround

    @property
    def {{callback_var_name}}(self):
        return self._{{callback_var_name}}

    @{{callback_var_name}}.setter
    def {{callback_var_name}}(self, cb):
        self._{{callback_var_name}} = cb

    {{/callbacks}}
    {{#properties}}
    {{#getter}}
    @property
    def {{python_name}}(self):
        {{#write_only}}
        raise AttributeError('Unreadable attribute')
        {{/write_only}}
        {{^write_only}}
        {{#is_return_obj_list}}
        _list = {{c_name}}(self.native_ptr)
        plist = []
        if _list == NULL:
            return plist
        bctbx_list = BctbxList.from_ptr(_list{{^is_const}}, True{{/is_const}})
        for i in range(0, bctbx_list.size):
            pt = {{python_return_type}}.from_ptr(<{{c_return_type}} *>bctbx_list.elem_at(i){{#constructor}}, False{{/constructor}})
            plist.append(pt)
        return plist
        {{/is_return_obj_list}}
        {{#is_return_string_list}}
        _list = {{c_name}}(self.native_ptr)
        if _list == NULL:
            return []
        return BctbxList.from_ptr(_list{{^is_const}}, True{{/is_const}}).to_string_array()
        {{/is_return_string_list}}
        {{#is_return_obj}}
        return {{python_return_type}}.from_ptr({{c_name}}(self.native_ptr){{#constructor}}, False{{/constructor}})
        {{/is_return_obj}}
        {{#is_return_bool}}
        return True if {{c_name}}(self.native_ptr) == 1 else False
        {{/is_return_bool}}
        {{#is_return_void_ptr}}
        return <object>{{c_name}}(self.native_ptr)
        {{/is_return_void_ptr}}
        {{#is_return_string}}
        cdef {{#is_return_const}}const {{/is_return_const}}char *return_s = {{c_name}}(self.native_ptr)
        if return_s == NULL:
            return None
        return return_s
        {{/is_return_string}}
        {{#is_simple_return}}
        return {{c_name}}(self.native_ptr)
        {{/is_simple_return}}
        {{/write_only}}
    {{/getter}}

    {{#setter}}
    @{{python_name}}.setter
    def {{python_name}}(self, value):
        {{#is_value_obj_list}}
        cdef BctbxList bctbx_list = BctbxList.__new__(BctbxList)
        for elem in value or []:
            pt = <{{python_value_type}}>elem
            bctbx_list.append(pt.native_ptr)
        {{c_name}}(self.native_ptr, bctbx_list.native_ptr)
        {{/is_value_obj_list}}
        {{#is_value_string_list}}
        bctbx_list = BctbxList.from_string_array(value)
        {{c_name}}(self.native_ptr, bctbx_list.native_ptr)
        {{/is_value_string_list}}
        {{#is_value_obj}}
        _value = <{{python_value_type}}>value
        {{c_name}}(self.native_ptr, _value.native_ptr)
        {{/is_value_obj}}
        {{#is_value_bool}}
        {{c_name}}(self.native_ptr, 1 if value else 0)
        {{/is_value_bool}}
        {{#is_value_void_ptr}}
        {{c_name}}(self.native_ptr, <void*>value)
        {{/is_value_void_ptr}}
        {{#is_value_string}}
        cdef char* value_s = NULL
        if value is not None:
            value_s = value
        {{c_name}}(self.native_ptr, value_s)
        {{/is_value_string}}
        {{#is_simple_value}}
        {{c_name}}(self.native_ptr, value)
        {{/is_simple_value}}
    {{/setter}}

    {{/properties}}
    {{#methods}}
    {{#is_static}}@staticmethod{{/is_static}}
    def {{python_name}}({{^is_static}}self{{/is_static}}{{python_params}}):
        {{#params}}
        {{#is_obj_list}}
        cdef BctbxList bctbx_list = BctbxList.__new__(BctbxList)
        for elem in {{python_param_name}} or []:
            pt = <{{python_param_type}}>elem
            bctbx_list.append(pt.native_ptr)
        {{python_param_name}}_list = bctbx_list.native_ptr
        {{/is_obj_list}}
        {{#is_string_list}}
        bctbx_list = BctbxList.from_string_array({{python_param_name}})
        {{python_param_name}}_list = bctbx_list.native_ptr
        {{/is_string_list}}
        {{#is_obj}}
        _{{python_param_name}} = <{{python_param_type}}>{{python_param_name}}
        {{python_param_name}}_ptr = _{{python_param_name}}.native_ptr if _{{python_param_name}} is not None else NULL
        {{/is_obj}}
        {{#is_bool}}
        {{python_param_name}}_b = 1 if {{python_param_name}} else 0
        {{/is_bool}}
        {{#is_void_ptr}}
        {{python_param_name}}_vptr = <void*>{{python_param_name}}
        {{/is_void_ptr}}
        {{#is_string}}
        cdef char* {{python_param_name}}_s = NULL
        if {{python_param_name}} is not None:
            {{python_param_name}}_s = {{python_param_name}}
        {{/is_string}}
        {{/params}}
        {{#has_return}}ret = {{/has_return}}{{c_name}}({{^is_static}}self.native_ptr{{/is_static}}{{computed_params}})
        {{#has_return_obj_list}}
        plist = []
        if ret == NULL:
            return plist
        bctbx_list = BctbxList.from_ptr(ret{{^is_const}}, True{{/is_const}})
        for i in range(0, bctbx_list.size):
            pt = {{return_python_type}}.from_ptr(<{{return_c_type}} *>bctbx_list.elem_at(i){{#constructor}}, False{{/constructor}})
            plist.append(pt)
        return plist
        {{/has_return_obj_list}}
        {{#has_return_string_list}}
        if ret == NULL:
            return []
        return BctbxList.from_ptr(ret{{^is_const}}, True{{/is_const}}).to_string_array()
        {{/has_return_string_list}}
        {{#has_return_obj}}
        return {{return_python_type}}.from_ptr(ret{{#constructor}}, False{{/constructor}})
        {{/has_return_obj}}
        {{#has_return_bool}}
        return True if ret == 1 else False
        {{/has_return_bool}}
        {{#has_return_string}}
        cdef {{#is_return_const}}const {{/is_return_const}}char *return_s = ret
        if return_s == NULL:
            return None
        return return_s
        {{/has_return_string}}
        {{#has_return_void_vptr}}
        return <object>ret
        {{/has_return_void_vptr}}
        {{#has_simple_return}}
        return ret
        {{/has_simple_return}}

    {{/methods}}
{{/objects}}
